package orm

import (
	"database/sql"
	"fmt"
	"gitee.com/lv_baobao/gcore/helper"
	"gitee.com/lv_baobao/gcore/logHelper"
	"github.com/hashicorp/go-uuid"
	"os"
	"reflect"
	"strings"
)

type Engine struct {
	db        *sql.DB
	dialect   string
	tableName string
	whereMap  map[string]interface{}
	showSql   bool
}

func NewEngine(driver, source string) (e *Engine, err error) {
	db, err := sql.Open(driver, source)
	if err != nil {
		logHelper.Error(err)
		return
	}
	// Send a ping to make sure the database connection is alive.
	if err = db.Ping(); err != nil {
		logHelper.Error(err)
		return
	}
	e = &Engine{db: db, dialect: driver, whereMap: map[string]interface{}{}}
	logHelper.Info("Connect database success")
	return
}

func (e *Engine) ShowSql(showSql bool) {
	e.showSql = showSql
}

func (e *Engine) InsertOne(i interface{}) (int64, error) {
	schema := NewSchema(i)
	sqlVars := schema.GetInsertRecordValues(nil)
	var sql = strings.Builder{}
	var insertFieldNames = make([]string, 0)
	for _, field := range schema.Fields {
		if !field.AutoIncr {
			insertFieldNames = append(insertFieldNames, field.Name)
		}
	}
	table := e.GetTable()
	if table == "" {
		table = schema.Name
	}
	sql.WriteString(fmt.Sprintf("insert into %s(%s) VALUES", table, strings.Join(insertFieldNames, ",")))

	for idx, _ := range insertFieldNames {
		if idx == 0 {
			sql.WriteString("(?")
		} else if idx == (len(insertFieldNames) - 1) {
			sql.WriteString(",?)")
		} else {
			sql.WriteString(",?")
		}
	}
	if schema.AutoPkField != nil {
		sql.WriteString(";select SCOPE_IDENTITY() id")
	}
	//logHelper.Info(sql.String(), sqlVars)
	if schema.AutoPkField != nil {
		var id int
		err := e.db.QueryRow(sql.String(), sqlVars...).Scan(&id)
		if err != nil {
			return 0, err
		}
		idValue := reflect.Indirect(reflect.ValueOf(i)).FieldByName(schema.AutoPkField.Name)
		if idValue.CanSet() {
			idValue.Set(reflect.ValueOf(id))
		}
	} else {
		var exec, err = e.db.Exec(sql.String(), sqlVars...)
		if err != nil {
			return 0, err
		}
		return exec.RowsAffected()
	}
	return 1, nil
}

func (e *Engine) Insert(list interface{}) (int64, error) {
	indirect := reflect.Indirect(reflect.ValueOf(list))
	listLen := indirect.Len()
	if listLen == 0 {
		return 0, nil
	}
	i := indirect.Index(0).Interface()
	schema := NewSchema(i)

	var sql = strings.Builder{}
	var insertFieldNames = make([]string, 0)
	for _, field := range schema.Fields {
		if !field.AutoIncr {
			insertFieldNames = append(insertFieldNames, field.Name)
		}
	}
	table := e.GetTable()
	if table == "" {
		table = schema.Name
	}
	sql.WriteString(fmt.Sprintf("insert into %s(%s) VALUES", table, strings.Join(insertFieldNames, ",")))

	var sqlVars = make([]interface{}, 0)
	for listIdx := 0; listIdx < listLen; listIdx++ {
		if listIdx > 0 {
			sql.WriteString(",")
		}
		for idx, _ := range insertFieldNames {
			if idx == 0 {
				sql.WriteString("(?")
			} else if idx == (len(insertFieldNames) - 1) {
				sql.WriteString(",?)")
			} else {
				sql.WriteString(",?")
			}
		}
		sqlVars = append(sqlVars, schema.GetInsertRecordValues(indirect.Index(listIdx).Interface())...)
	}
	logHelper.Info(sql.String(), sqlVars)
	var exec, err = e.db.Exec(sql.String(), sqlVars...)
	if err != nil {
		return 0, err
	}
	return exec.RowsAffected()
}

func (e *Engine) BulkInsert(list interface{}) (int64, error) {
	indirect := reflect.Indirect(reflect.ValueOf(list))
	listLen := indirect.Len()
	if listLen == 0 {
		return 0, nil
	}
	i := indirect.Index(0).Interface()
	schema := NewSchema(i)
	var insertFieldNames = make([]string, 0)
	for _, field := range schema.Fields {
		if !field.AutoIncr {
			insertFieldNames = append(insertFieldNames, field.Name)
		}
	}
	table := e.GetTable()
	if table == "" {
		table = schema.Name
	}
	generateUUID, _ := uuid.GenerateUUID()
	fileName := fmt.Sprintf("%s/%s.txt", helper.GetAbsPath(), generateUUID)
	fmt.Println("batchInsert FileName is ", fileName)
	fl, err := os.OpenFile(fileName, os.O_CREATE, 0644)
	if err != nil {
		panic(err)
	}
	defer fl.Close()
	for listIdx := 0; listIdx < listLen; listIdx++ {
		values := schema.GetRecordValues(indirect.Index(listIdx).Interface())
		for valIdx, value := range values {
			getBytes := GetBytes(value)
			if valIdx != (len(values) - 1) {
				elems := []byte(",")
				getBytes = append(getBytes, elems...)
			}
			_, err := fl.Write(getBytes)
			if err != nil {
				panic(err)
			}
		}
		if listIdx != (listLen - 1) {
			fl.Write([]byte(";\r\n"))
		}
	}
	fmt.Println(helper.GetCurrentDirectory())
	var sql = fmt.Sprintf("bulk insert %s from '%s' with ( FIRSTROW = 1, FIELDTERMINATOR = ',',ROWTERMINATOR = ';',TABLOCK)", table, fileName)
	fmt.Println(sql)
	fl.Close()
	exec, err := e.db.Exec(sql)
	os.Remove(fileName)
	if err != nil {
		logHelper.Error(err)
		return 0, err
	}
	return exec.RowsAffected()
}

func GetBytes(key interface{}) []byte {
	return []byte(fmt.Sprint(key))

	//s,ok := key.(string)
	//if ok{
	//	return []byte(s)
	//}
	//i,ok := key.(int)
	//if ok{
	//	return []byte(strconv.Itoa(i))
	//}
	//cInt,ok := key.(helper.C_int)
	//if ok{
	//	return []byte(strconv.Itoa(int(cInt)))
	//}
	//var buf bytes.Buffer
	//enc := gob.NewEncoder(&buf)
	//err := enc.Encode(key)
	//if err != nil {
	//	return nil
	//}
	//return buf.Bytes()
}

func (e *Engine) Update(i interface{}) (int64, error) {
	var sql = strings.Builder{}
	schema := NewSchema(i)
	table := e.GetTable()
	if table == "" {
		table = schema.Name
	}
	sql.WriteString(fmt.Sprintf("update %s set ", table))
	var pkField *Field = schema.PkField
	var isFirstSet = true
	for _, field := range schema.Fields {
		if field.IsPk {
			continue
		}
		if isFirstSet {
			sql.WriteString(fmt.Sprintf("%s=?", field.Name))
			isFirstSet = false
		} else {
			sql.WriteString(fmt.Sprintf(",%s=?", field.Name))
		}
	}
	sql.WriteString(fmt.Sprintf(" where %s=?", pkField.Name))
	sqlVars := schema.GetUpdateRecordValues(i)
	logHelper.Info(sql.String(), sqlVars)
	exec, err := e.db.Exec(sql.String(), sqlVars...)
	if err != nil {
		return 0, err
	}
	return exec.RowsAffected()
}

func (e *Engine) Delete(i interface{}) (int64, error) {
	var sql = strings.Builder{}
	schema := NewSchema(i)
	table := e.GetTable()
	if table == "" {
		table = schema.Name
	}
	sql.WriteString(fmt.Sprintf("delete from %s where ", table))
	var pkField *Field = schema.PkField
	sql.WriteString(fmt.Sprintf(" %s=?", pkField.Name))
	fieldValue := schema.GetFieldValue(schema.Value, *pkField)
	logHelper.Info(sql.String(), fieldValue)
	exec, err := e.db.Exec(sql.String(), fieldValue)
	if err != nil {
		return 0, err
	}
	return exec.RowsAffected()
}

func (e *Engine) Count() (int64, error) {
	var sql = strings.Builder{}
	table := e.GetTable()
	sql.WriteString(fmt.Sprintf("select count(0) as tableRowsCount from %s ", table))
	_, whereArgs, whereSql := e.getWhereKeysAndArgsAndWhereSql()
	if whereSql != "" {
		sql.WriteString(whereSql)
	}
	var args = make([]interface{}, 0)
	if len(whereArgs) > 0 {
		args = append(args, whereArgs...)
	}
	logHelper.Info(sql.String(), args)
	var totalRecords int64 = 0
	err := e.db.QueryRow(sql.String(), args...).Scan(&totalRecords)
	if err != nil {
		return 0, err
	}
	return totalRecords, nil
}

func (e *Engine) Get(itemPtr interface{}) error {
	schema := NewSchema(itemPtr)

	var sql = strings.Builder{}
	table := e.GetTable()
	sql.WriteString(fmt.Sprintf("select %s from %s ", strings.Join(schema.FieldNames, ","), table))
	_, whereArgs, whereSql := e.getWhereKeysAndArgsAndWhereSql()
	if whereSql != "" {
		sql.WriteString(whereSql)
	}
	var args = make([]interface{}, 0)
	if len(whereArgs) > 0 {
		args = append(args, whereArgs...)
	}
	logHelper.Info(sql.String(), args)
	var addressList = make([]interface{}, 0)
	for i := 0; i < len(schema.Fields); i++ {
		field := schema.Value.Field(i)
		if !field.CanAddr() {
			return nil
		}
		i2 := field.Addr().Interface()
		addressList = append(addressList, i2)
	}
	err := e.db.QueryRow(sql.String(), args...).Scan(addressList...)
	fmt.Println(itemPtr)
	return err
}

func (e *Engine) List(listPtr interface{}) error {
	schema := NewSchema(listPtr)
	var sql = strings.Builder{}
	table := e.GetTable()
	sql.WriteString(fmt.Sprintf("select %s from %s ", strings.Join(schema.FieldNames, ","), table))
	_, whereArgs, whereSql := e.getWhereKeysAndArgsAndWhereSql()
	if whereSql != "" {
		sql.WriteString(whereSql)
	}
	var args = make([]interface{}, 0)
	if len(whereArgs) > 0 {
		args = append(args, whereArgs...)
	}
	logHelper.Info(sql.String(), args)
	rows, err := e.db.Query(sql.String(), args...)
	if err != nil {
		return err
	}
	for rows.Next() {
		v := reflect.New(schema.ModelType).Elem()
		var addressList = make([]interface{}, 0)
		for i := 0; i < len(schema.Fields); i++ {
			addressList = append(addressList, v.Field(i).Addr().Interface())
		}
		rows.Scan(addressList...)
		//v2 := reflect.Append(reflect.ValueOf(listPtr).Elem(), v)
		v2 := reflect.Append(schema.Value, v)
		if !schema.Value.CanSet() {
			return nil
		}
		schema.Value.Set(v2)
	}
	return nil
}

func (e *Engine) WhereIf(condition string, arg interface{}, conditionTrue bool) *Engine {
	if conditionTrue {
		e.whereMap[condition] = arg
	}
	return e
}

func (e *Engine) Where(condition string, arg interface{}) *Engine {
	return e.WhereIf(condition, arg, true)
}

func (e *Engine) Table(table string) *Engine {
	e.tableName = table
	return e
}

func (e *Engine) GetTable() string {
	return e.tableName
}

func (e *Engine) Close() error {
	return e.db.Close()
}

func (e *Engine) getWhereKeysAndArgsAndWhereSql() ([]string, []interface{}, string) {
	var whereArgs = make([]interface{}, 0)
	var whereKeys = make([]string, 0)
	if len(e.whereMap) > 0 {
		for k := range e.whereMap {
			whereKeys = append(whereKeys, k)
		}
	}
	for _, key := range whereKeys {
		if e.whereMap[key] != nil {
			whereArgs = append(whereArgs, e.whereMap[key])
		}
	}
	whereSql := strings.Builder{}
	for index, key := range whereKeys {
		if index == 0 {
			whereSql.WriteString(fmt.Sprintf(" where 1=1 %s ", key))
		} else {
			whereSql.WriteString(fmt.Sprintf(" %s ", key))
		}
	}
	return whereKeys, whereArgs, whereSql.String()
}
